package com.yincheng.oes.business.service.impl;

import com.yincheng.oes.attachment.entity.Attachment;
import com.yincheng.oes.attachment.mapper.AttachmentMapper;
import com.yincheng.oes.business.consist.ResultCodeEnum;
import com.yincheng.oes.business.dao.PaperOutlineDao;
import com.yincheng.oes.business.dao.StudentPaperDao;
import com.yincheng.oes.business.dao.StudentPaperQuestionDao;
import com.yincheng.oes.business.model.dto.DownloadFileDTO;
import com.yincheng.oes.business.model.dto.PageResultDTO;
import com.yincheng.oes.business.model.entity.ExportStudentPaperDO;
import com.yincheng.oes.business.model.entity.ExportStudentPaperKnowledgeDO;
import com.yincheng.oes.business.model.entity.ExportStudentPaperQuestionsDO;
import com.yincheng.oes.business.model.export.dto.ExportStudentPaperDTO;
import com.yincheng.oes.business.model.export.dto.ExportStudentPaperErrorKnowledgeDTO;
import com.yincheng.oes.business.model.export.dto.ExportStudentPaperQuestionDTO;
import com.yincheng.oes.business.model.query.DownloadListReq;
import com.yincheng.oes.business.model.query.ExportStudentPaperReq;
import com.yincheng.oes.business.model.result.BusinessResult;
import com.yincheng.oes.business.repository.FileResourceManager;
import com.yincheng.oes.business.service.ExportService;
import com.yincheng.oes.business.util.*;
import com.yincheng.oes.sso.util.UserUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * @Author: yangsy
 * @Contact: 446948434@qq.com
 * @Date: 2021/9/5 16:25
 * @Version: 1.0
 * @Description:
 */
@Service
public class ExportServiceImpl implements ExportService {
    /**
     * 日志
     */
    private static final Logger logger = LogManager.getLogger(ExportServiceImpl.class);

    private static final String EXPORT_STUDENT_WORD_TEM_DIR = "导出学生成绩";

    private static final String EXPORT_STUDENT_TMP_FILE_NAME = "导出学员成绩.docx";

    private static final String TMP_ROOT_KEY = "temp_path";

    private final StudentPaperDao studentPaperDao;

    private final PaperOutlineDao paperOutlineDao;

    private final StudentPaperQuestionDao studentPaperQuestionDao;
    private final ExportWordUtils exportWordUtils;
    private final AttachmentMapper attachmentMapper;

    private final FileResourceManager fileResourceManager;

    /**
     * 线程池，用来执行导出程序
     */
    private final static ExecutorService singleExecutor = Executors.newSingleThreadExecutor();

    public ExportServiceImpl(StudentPaperDao studentPaperDao, PaperOutlineDao paperOutlineDao, StudentPaperQuestionDao studentPaperQuestionDao, ExportWordUtils exportWordUtils, AttachmentMapper attachmentMapper, FileResourceManager fileResourceManager) {
        this.studentPaperDao = studentPaperDao;
        this.paperOutlineDao = paperOutlineDao;
        this.studentPaperQuestionDao = studentPaperQuestionDao;
        this.exportWordUtils = exportWordUtils;
        this.attachmentMapper = attachmentMapper;
        this.fileResourceManager = fileResourceManager;
    }

    @Override
    public BusinessResult exportStudentPapers(ExportStudentPaperReq req) {
        logger.debug("start exportStudentPapers, req is {}", req);
        BusinessResult businessResult;
        if (Objects.isNull(req) || CollectionUtils.isEmpty(req.getResultPaperIds())) {
            logger.error("exportStudentPapers param invalid, req is {}", req);
            return new BusinessResult(ResultCodeEnum.NO_QUERY_PARAM);
        }
        String userId = UserUtils.getUser().getUserId();
        //异步导出试卷
        singleExecutor.execute(() -> exportStudentPapersSync(req, userId));
        businessResult = new BusinessResult(ResultCodeEnum.SUCCESS);
        return businessResult;
    }

    @Transactional
    public void exportStudentPapersSync(ExportStudentPaperReq req, String userId) {
        //1.获取导出试卷集合
        List<ExportStudentPaperDTO> studentPapers = getStudentPapers(req);
        // 2.生成zip文档
        Attachment attachment = generateStudentPapersDocZip(studentPapers, userId);

        //3.更新试卷的导出状态信息
        studentPaperDao.batchUpdateStudentPaperExportStatus(req.getResultPaperIds());

        //4.将zip信息保存到数据库中
        attachmentMapper.insert(attachment);
    }

    @Override
    public Pair<Integer, List<DownloadFileDTO>> getListByParam(DownloadListReq param) {
        if (Objects.isNull(param)) {
            return null;
        }
        PageResultDTO<Attachment> pageResultDTO = fileResourceManager.pagingQuery(param);
        return Pair.of(pageResultDTO.getTotal(), buildDownloadFiles(pageResultDTO.getResults()));
    }

    private List<DownloadFileDTO> buildDownloadFiles(List<Attachment> attachments) {
        if (CollectionUtils.isEmpty(attachments)) {
            return new ArrayList<>();
        }
        return attachments.stream().map(this::buildDownloadFileDTO).collect(Collectors.toList());
    }

    private DownloadFileDTO buildDownloadFileDTO(Attachment attachment) {
        DownloadFileDTO dto = new DownloadFileDTO();
        if (Objects.isNull(attachment)) {
            return dto;
        }
        dto.setFileName(attachment.getFileName());
        dto.setCreateTime(attachment.getStoreTime());
        dto.setId(attachment.getAttachmentId());
        return dto;
    }

    private List<ExportStudentPaperDTO> getStudentPapers(ExportStudentPaperReq req) {
        List<ExportStudentPaperDTO> studentPapers = new ArrayList<>();

        // 1.批量查询答卷信息
        List<ExportStudentPaperDO> exportStudentPaperDOS = studentPaperDao.batchSearchStudentPapers(req.getResultPaperIds());
        if (CollectionUtils.isEmpty(exportStudentPaperDOS)) {
            logger.error("batchSearchStudentPapers is null, req is {}", req);
            return studentPapers;
        }
        // 2.批量查询答卷关联的大纲信息
        List<ExportStudentPaperKnowledgeDO> exportStudentPaperKnowledgeDOS = paperOutlineDao.batchSearchPaperResultOutlines(req.getResultPaperIds());
        if (CollectionUtils.isEmpty(exportStudentPaperKnowledgeDOS)) {
            logger.error("exportStudentPaperErrorKnowledgeDOS is null, req is {}", req);
        }
        // 3.批量查询答卷的题目信息
        List<ExportStudentPaperQuestionsDO> exportStudentPaperQuestionsDOS = studentPaperQuestionDao.batchSearchStudentPaperQuestions(req.getResultPaperIds());
        if (CollectionUtils.isEmpty(exportStudentPaperQuestionsDOS)) {
            logger.error("batchSearchStudentPaperQuestions is null, req is {}", req);
        }
        // 4.答卷转换为导出试卷信息
        studentPapers = buildStudentPapers(exportStudentPaperDOS);
        // 5.将知识点和题目信息封装成 ExportStudentPaperQuestionDTO List
        List<ExportStudentPaperQuestionDTO> exportStudentPaperQuestions = buildQuestionWithKnowledge(exportStudentPaperQuestionsDOS, exportStudentPaperKnowledgeDOS);
        // 6.构建每个答卷的错题关联的知识点集合
        List<ExportStudentPaperErrorKnowledgeDTO> exportStudentPaperErrorKnowledgeDTOList = buildErrorKnowledge(exportStudentPaperQuestions);
        // 7.构建<答卷id,题目集合>map
        Map<String, List<ExportStudentPaperQuestionDTO>> exportStudentPaperQuestionMap = exportStudentPaperQuestions.stream().collect(Collectors.groupingBy(ExportStudentPaperQuestionDTO::getPaperResultId));
        // 8.构建<答卷id,错题知识点集合>map
        Map<String, List<ExportStudentPaperErrorKnowledgeDTO>> exportStudentPaperErrorKnowledgeMap = exportStudentPaperErrorKnowledgeDTOList.stream().collect(Collectors.groupingBy(ExportStudentPaperErrorKnowledgeDTO::getPaperResultId));
        // 9.封装答卷的题目信息和错题知识点信息
        studentPapers.forEach(e -> {
            List<ExportStudentPaperErrorKnowledgeDTO> questions = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(exportStudentPaperErrorKnowledgeMap.get(e.getPaperResultId()))) {
                //题目信息按照题目序号排序
                questions = exportStudentPaperErrorKnowledgeMap.get(e.getPaperResultId()).stream().sorted(Comparator.comparing(ExportStudentPaperErrorKnowledgeDTO::getQuesOrder)).collect(Collectors.toList());
            }
            e.setErrorKnowledgeList(questions);
            e.setQuestions(exportStudentPaperQuestionMap.get(e.getPaperResultId()));
        });

        return studentPapers;
    }

    private Attachment generateStudentPapersDocZip(List<ExportStudentPaperDTO> studentPapers, String userId) {
        if (CollectionUtils.isEmpty(studentPapers)) {
            return null;
        }

        String fileName = studentPapers.get(0).getUserName() + "_" + TimeUtils.getYyMMddHHmm() + ".zip";

        File zipFile = FileUtils.getFile(ExportFileUtils.getTmpBaseFile(), fileName);

        ZipArchiveOutputStream zaos = null;
        try {
            zaos = new ZipArchiveOutputStream(zipFile);

            for (ExportStudentPaperDTO studentPaper : studentPapers) {
                try {
                    String paperName = studentPaper.getPaperName() + ".docx";
                    File word = exportWordUtils.createWord(studentPaper, EXPORT_STUDENT_TMP_FILE_NAME, EXPORT_STUDENT_WORD_TEM_DIR);

                    ZipArchiveEntry archiveEntry = new ZipArchiveEntry(paperName);
                    archiveEntry.setSize(word.length());
                    zaos.putArchiveEntry(archiveEntry);
                    FileUtils.copyFile(word, zaos);
                } catch (IOException e) {
                    logger.error("generate archiveEntry occurred error", e);
                } finally {
                    try {
                        zaos.closeArchiveEntry();
                        //TODO 创建定时任务删除临时文件 这里的tmpFile还被zaos占用，无法直接删除
                        // FileUtils.forceDeleteOnExit(tmpFile);
                    } catch (IOException e) {
                        logger.error("generate archiveEntry occurred error", e);
                    }
                }
            }
        } catch (IOException e) {
            logger.error("get ZipArchiveOutputStream occurred error", e);
            return null;
        } finally {
            try {
                assert zaos != null;
                zaos.close();
            } catch (IOException e) {
                logger.error("zipArchiveOutputStream close error", e);
            }
        }
        return AttachmentUtils.getAttachment(fileName, zipFile.length(), ".zip", userId);
    }

    private List<ExportStudentPaperErrorKnowledgeDTO> buildErrorKnowledge(List<ExportStudentPaperQuestionDTO> exportStudentPaperQuestions) {
        List<ExportStudentPaperErrorKnowledgeDTO> exportStudentPaperErrorKnowledgeDTOList = new ArrayList<>();
        if (CollectionUtils.isEmpty(exportStudentPaperQuestions)) {
            return exportStudentPaperErrorKnowledgeDTOList;
        }

        exportStudentPaperQuestions.forEach(e -> {
            if (StringUtils.equals("0", e.getCorrectFlag())) {
                e.getContents().forEach(content -> {
                    ExportStudentPaperErrorKnowledgeDTO exportStudentPaperErrorKnowledgeDTO = new ExportStudentPaperErrorKnowledgeDTO();
                    exportStudentPaperErrorKnowledgeDTO.setFullPath(content);
                    exportStudentPaperErrorKnowledgeDTO.setQuesOrder(StringUtil.valueOf(e.getQuesOrder()));
                    exportStudentPaperErrorKnowledgeDTO.setPaperResultId(e.getPaperResultId());
                    exportStudentPaperErrorKnowledgeDTOList.add(exportStudentPaperErrorKnowledgeDTO);
                });
            }
        });
        return exportStudentPaperErrorKnowledgeDTOList;
    }

    private List<ExportStudentPaperQuestionDTO> buildQuestionWithKnowledge(List<ExportStudentPaperQuestionsDO> exportStudentPaperQuestionsDOS, List<ExportStudentPaperKnowledgeDO> exportStudentPaperKnowledgeDOS) {
        List<ExportStudentPaperQuestionDTO> exportStudentPaperQuestionDTOS = new ArrayList<>();
        if (CollectionUtils.isEmpty(exportStudentPaperQuestionsDOS) || CollectionUtils.isEmpty(exportStudentPaperKnowledgeDOS)) {
            return exportStudentPaperQuestionDTOS;
        }
        Map<Integer, Set<String>> quesIdKnowledgeMap = exportStudentPaperKnowledgeDOS.stream().collect(Collectors.groupingBy(ExportStudentPaperKnowledgeDO::getQuesId, Collectors.mapping(ExportStudentPaperKnowledgeDO::getFullPath, Collectors.toSet())));

        exportStudentPaperQuestionDTOS = exportStudentPaperQuestionsDOS.stream().filter(Objects::nonNull).map(e -> {
            ExportStudentPaperQuestionDTO exportStudentPaperQuestion = new ExportStudentPaperQuestionDTO();
            exportStudentPaperQuestion.setAnalysis(e.getAnalysis());
            exportStudentPaperQuestion.setAnswer(e.getAnswer());
            Set<String> set = quesIdKnowledgeMap.get(e.getQuesId());
            if (CollectionUtils.isEmpty(set)) {
                logger.warn("the question is {}, the outlines is null", e);
                exportStudentPaperQuestion.setContents(new ArrayList<>(Arrays.asList("")));
            } else {
                exportStudentPaperQuestion.setContents(new ArrayList<>(set));
            }
            exportStudentPaperQuestion.setMyAnswer(e.getMyAnswer());
            exportStudentPaperQuestion.setOptions(e.getOptions());
            exportStudentPaperQuestion.setOptionTitles(e.getOptionTitles());
            exportStudentPaperQuestion.setStem(e.getStem());
            exportStudentPaperQuestion.setBranch(e.getBranch());
            exportStudentPaperQuestion.setCorrectFlag(e.getCorrectFlag());
            exportStudentPaperQuestion.setPaperResultId(e.getPaperResultId());
            exportStudentPaperQuestion.setQuesOrder(e.getQuesOrder() + 1);
            return exportStudentPaperQuestion;
        }).collect(Collectors.toList());
        logger.debug("after buildQuestionWithKnowledge exportStudentPaperQuestions are {}", exportStudentPaperQuestionsDOS);
        return exportStudentPaperQuestionDTOS;
    }

    private List<ExportStudentPaperDTO> buildStudentPapers(List<ExportStudentPaperDO> exportStudentPaperDOS) {
        List<ExportStudentPaperDTO> studentPapers = exportStudentPaperDOS.stream().filter(Objects::nonNull).map(e -> {
            ExportStudentPaperDTO exportStudentPaper = new ExportStudentPaperDTO();
            exportStudentPaper.setPaperResultId(e.getPaperResultId());
            exportStudentPaper.setPaperName(e.getPaperName());
            exportStudentPaper.setScore(DigitalUtils.format(e.getScore()));
            exportStudentPaper.setTotalScore(DigitalUtils.format(e.getTotalScore()));
            exportStudentPaper.setUserName(e.getUserName());

            exportStudentPaper.setTitle(e.getPaperName());
            return exportStudentPaper;
        }).collect(Collectors.toList());
        logger.debug("after buildStudentPapers studentPapers are {}", studentPapers);
        return studentPapers;

    }
}
